/*-------------------------------------------------------------------------
 *
 * indextuple.c
 *	   此文件包含索引元组的访问器和修改器例程，
 *	   以及各种元组工具。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/access/common/indextuple.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/detoast.h"
#include "access/heaptoast.h"
#include "access/htup_details.h"
#include "access/itup.h"
#include "access/toast_internals.h"

/*
 * 这启用了索引条目的去烘焙。在 VACUUM 足够智能到可以从头重建索引之前是必要的。
 */
#define TOAST_INDEX_HACK

/* ----------------------------------------------------------------
 *				  索引元组接口例程
 * ----------------------------------------------------------------
 */

 /* ----------------
  *		index_form_tuple
  *
  *		与 index_form_tuple_context 类似，但将返回的元组分配在
  *		CurrentMemoryContext 中。
  * ----------------
  */
IndexTuple index_form_tuple(TupleDesc fc_tupleDescriptor,
				 Datum *fc_values,
				 bool *fc_isnull)
{
	return index_form_tuple_context(fc_tupleDescriptor, fc_values, fc_isnull,
									CurrentMemoryContext);
}

/* ----------------
 *		index_form_tuple_context
 *
 *		这不应该泄露任何内存；否则，像 tuplesort_putindextuplevalues()
 *		这样的调用者将非常不开心。
 *
 *		如果调用者不传递存储为 EXTERNAL 的值，则不应执行外部表
 *		访问。
 *
 *		在提供的 'context' 中分配返回的元组。
 * ----------------
 */
IndexTuple index_form_tuple_context(TupleDesc fc_tupleDescriptor,
						 Datum *fc_values,
						 bool *fc_isnull,
						 MemoryContext fc_context)
{
	char	   *fc_tp;				/* 元组指针 */
	IndexTuple	fc_tuple;			/* 返回元组 */
	Size		fc_size,
				fc_data_size,
				fc_hoff;
	int			fc_i;
	unsigned short fc_infomask = 0;
	bool		fc_hasnull = false;
	uint16		fc_tupmask = 0;
	int			fc_numberOfAttributes = fc_tupleDescriptor->natts;

#ifdef TOAST_INDEX_HACK
	Datum		fc_untoasted_values[INDEX_MAX_KEYS];
	bool		fc_untoasted_free[INDEX_MAX_KEYS];
#endif

	if (fc_numberOfAttributes > INDEX_MAX_KEYS)
		ereport(ERROR,
				(errcode(ERRCODE_TOO_MANY_COLUMNS),
				 errmsg("number of index columns (%d) exceeds limit (%d)",
						fc_numberOfAttributes, INDEX_MAX_KEYS)));

#ifdef TOAST_INDEX_HACK
	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDescriptor, fc_i);

		fc_untoasted_values[fc_i] = fc_values[fc_i];
		fc_untoasted_free[fc_i] = false;

		/* 如果值为 NULL 或不属于 varlena 类型则不执行任何操作 */
		if (fc_isnull[fc_i] || fc_att->attlen != -1)
			continue;

		/*
		 * 如果值存储为 EXTERNAL，必须获取它，以便不依赖
		 * 外部存储。这在未来某天应该得到改善。
		 */
		if (VARATT_IS_EXTERNAL(DatumGetPointer(fc_values[fc_i])))
		{
			fc_untoasted_values[fc_i] =
				PointerGetDatum(detoast_external_attr((struct varlena *)
													  DatumGetPointer(fc_values[fc_i])));
			fc_untoasted_free[fc_i] = true;
		}

		/*
		 * 如果值超过大小目标，并且是可压缩的数据类型，
		 * 尝试在线压缩它。
		 */
		if (!VARATT_IS_EXTENDED(DatumGetPointer(fc_untoasted_values[fc_i])) &&
			VARSIZE(DatumGetPointer(fc_untoasted_values[fc_i])) > TOAST_INDEX_TARGET &&
			(fc_att->attstorage == TYPSTORAGE_EXTENDED ||
			 fc_att->attstorage == TYPSTORAGE_MAIN))
		{
			Datum		fc_cvalue;

			fc_cvalue = toast_compress_datum(fc_untoasted_values[fc_i],
										  fc_att->attcompression);

			if (DatumGetPointer(fc_cvalue) != NULL)
			{
				/* 成功的压缩 */
				if (fc_untoasted_free[fc_i])
					pfree(DatumGetPointer(fc_untoasted_values[fc_i]));
				fc_untoasted_values[fc_i] = fc_cvalue;
				fc_untoasted_free[fc_i] = true;
			}
		}
	}
#endif

	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		if (fc_isnull[fc_i])
		{
			fc_hasnull = true;
			break;
		}
	}

	if (fc_hasnull)
		fc_infomask |= INDEX_NULL_MASK;

	fc_hoff = IndexInfoFindDataOffset(fc_infomask);
#ifdef TOAST_INDEX_HACK
	fc_data_size = heap_compute_data_size(fc_tupleDescriptor,
									   fc_untoasted_values, fc_isnull);
#else
	fc_data_size = heap_compute_data_size(fc_tupleDescriptor,
									   fc_values, isnull);
#endif
	fc_size = fc_hoff + fc_data_size;
	fc_size = MAXALIGN(fc_size);		/* 保持保守 */

	fc_tp = (char *) MemoryContextAllocZero(fc_context, fc_size);
	fc_tuple = (IndexTuple) fc_tp;

	heap_fill_tuple(fc_tupleDescriptor,
#ifdef TOAST_INDEX_HACK
					fc_untoasted_values,
#else
					fc_values,
#endif
					fc_isnull,
					(char *) fc_tp + fc_hoff,
					fc_data_size,
					&fc_tupmask,
					(fc_hasnull ? (bits8 *) fc_tp + sizeof(IndexTupleData) : NULL));

#ifdef TOAST_INDEX_HACK
	for (fc_i = 0; fc_i < fc_numberOfAttributes; fc_i++)
	{
		if (fc_untoasted_free[fc_i])
			pfree(DatumGetPointer(fc_untoasted_values[fc_i]));
	}
#endif

	/*
	 * 我们这样做是因为 heap_fill_tuple 想要初始化一个 "tupmask"
	 * 用于 HeapTuples，但我们想要一个 indextuple infomask。唯一相关的
	 * 信息是“具有可变属性”字段。我们已经在上面设置了 hasnull 位。
	 */
	if (fc_tupmask & HEAP_HASVARWIDTH)
		fc_infomask |= INDEX_VAR_MASK;

	/* 还要断言我们已去除外部属性 */
#ifdef TOAST_INDEX_HACK
	Assert((fc_tupmask & HEAP_HASEXTERNAL) == 0);
#endif

	/*
	 * 在这里，我们确保大小能够放入 t_info 中为其预留的字段。
	 */
	if ((fc_size & INDEX_SIZE_MASK) != fc_size)
		ereport(ERROR,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				 errmsg("index row requires %zu bytes, maximum size is %zu",
						fc_size, (Size) INDEX_SIZE_MASK)));

	fc_infomask |= fc_size;

	/*
	 * 初始化元数据
	 */
	fc_tuple->t_info = fc_infomask;
	return fc_tuple;
}

/* ----------------
 *		nocache_index_getattr
 *
 *		该函数由 index_getattr() 宏调用，仅在
 *		不能使用 cacheoffset 且值不为空的情况下调用。
 *
 *		这将在属性描述符中缓存属性偏移量。
 *
 *		加速的另一种方式是将偏移量与元组一起缓存，但除非
 *		实际将这些偏移量放入发送到磁盘的元组中，否则这似乎更加困难。
 *		真糟糕。
 *
 *		这个方案将比那种方案稍慢，但对于命中大量元组的
 *		查询应该表现良好。在您缓存一次偏移量后，使用
 *		相同属性描述符检查所有其他元组将更快。 -cim 5/4/91
 * ----------------
 */
Datum nocache_index_getattr(IndexTuple fc_tup,
					  int fc_attnum,
					  TupleDesc fc_tupleDesc)
{
	char	   *fc_tp;				/* 指向元组数据部分的指针 */
	bits8	   *fc_bp = NULL;		/* 指向元组中 null 位图的指针 */
	bool		fc_slow = false;	/* 我们是否需要遍历属性？ */
	int			fc_data_off;		/* 元组数据偏移量 */
	int			fc_off;			/* 数据中的当前偏移量 */

	/* ----------------
	 *	 三种情况：
	 *
	 *	 1: 没有 null 和没有可变宽度属性。
	 *	 2: 在属性之后有一个 null 或可变宽度。
	 *	 3: 在属性之前有 null 或可变宽度。
	 * ----------------
	 */

	fc_data_off = IndexInfoFindDataOffset(fc_tup->t_info);

	fc_attnum--;

	if (IndexTupleHasNulls(fc_tup))
	{
		/*
		 * 元组中某处存在空值
		 *
		 * 检查所需的属性是否为null
		 */

		/* XXX "知道" t_bits 在固定元组头之后！ */
		fc_bp = (bits8 *) ((char *) fc_tup + sizeof(IndexTupleData));

		/*
		 * 现在检查前面的位是否为null...
		 */
		{
			int			fc_byte = fc_attnum >> 3;
			int			fc_finalbit = fc_attnum & 0x07;

			/* 检查最后一个字节的最终位“之前”的 null */
			if ((~fc_bp[fc_byte]) & ((1 << fc_finalbit) - 1))
				fc_slow = true;
			else
			{
				/* 检查任何“较早”字节中的 null */
				int			fc_i;

				for (fc_i = 0; fc_i < fc_byte; fc_i++)
				{
					if (fc_bp[fc_i] != 0xFF)
					{
						fc_slow = true;
						break;
					}
				}
			}
		}
	}

	fc_tp = (char *) fc_tup + fc_data_off;

	if (!fc_slow)
	{
		Form_pg_attribute fc_att;

		/*
		 * 如果我们到这里，目标属性之前（包括目标属性）没有 null。
		 * 如果我们有一个缓存偏移量，我们可以使用它。
		 */
		fc_att = TupleDescAttr(fc_tupleDesc, fc_attnum);
		if (fc_att->attcacheoff >= 0)
			return fetchatt(fc_att, fc_tp + fc_att->attcacheoff);

		/*
		 * 否则，检查目标属性之前（包括目标属性）是否有非固定长度的属性。
		 * 如果没有，它安全地便宜地初始化这些属性的缓存偏移量。
		 */
		if (IndexTupleHasVarwidths(fc_tup))
		{
			int			fc_j;

			for (fc_j = 0; fc_j <= fc_attnum; fc_j++)
			{
				if (TupleDescAttr(fc_tupleDesc, fc_j)->attlen <= 0)
				{
					fc_slow = true;
					break;
				}
			}
		}
	}

	if (!fc_slow)
	{
		int			fc_natts = fc_tupleDesc->natts;
		int			fc_j = 1;

		/*
		 * 如果我们到这里，我们有一个元组，到目标属性（包括目标属性）没有 null 或可变宽度，
		 * 因此我们可以使用缓存偏移量... 只不过我们还没有它，否则我们不会到这里。
		 * 由于计算固定宽列的偏移量是便宜的，我们抓住机会为*所有*前导固定宽列初始化缓存偏移量，
		 * 希望避免将来访问这个例程。
		 */
		TupleDescAttr(fc_tupleDesc, 0)->attcacheoff = 0;

		/* 我们可能在之前的慢路径中设置了一些偏移量 */
		while (fc_j < fc_natts && TupleDescAttr(fc_tupleDesc, fc_j)->attcacheoff > 0)
			fc_j++;

		fc_off = TupleDescAttr(fc_tupleDesc, fc_j - 1)->attcacheoff +
			TupleDescAttr(fc_tupleDesc, fc_j - 1)->attlen;

		for (; fc_j < fc_natts; fc_j++)
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_j);

			if (fc_att->attlen <= 0)
				break;

			fc_off = att_align_nominal(fc_off, fc_att->attalign);

			fc_att->attcacheoff = fc_off;

			fc_off += fc_att->attlen;
		}

		Assert(fc_j > fc_attnum);

		fc_off = TupleDescAttr(fc_tupleDesc, fc_attnum)->attcacheoff;
	}
	else
	{
		bool		fc_usecache = true;
		int			fc_i;

		/*
		 * 现在我们知道必须小心地遍历元组。但是我们仍然
		 * 可能能够缓存一些偏移量以便下次使用。
		 *
		 * 注意 - 这个循环有点棘手。对于每个非空属性，
		 * 我们必须首先计算属性之前的对齐填充，
		 * 然后根据其长度推进属性。空值没有
		 * 存储，也没有对齐填充。我们可以使用/设置
		 * attcacheoff，直到我们遇到空值或可变宽度属性。
		 */
		fc_off = 0;
		for (fc_i = 0;; fc_i++)		/* 循环退出在“break”处 */
		{
			Form_pg_attribute fc_att = TupleDescAttr(fc_tupleDesc, fc_i);

			if (IndexTupleHasNulls(fc_tup) && att_isnull(fc_i, fc_bp))
			{
				fc_usecache = false;
				continue;		/* 这不能是目标属性 */
			}

			/* 如果我们知道下一个偏移量，我们可以跳过其余部分 */
			if (fc_usecache && fc_att->attcacheoff >= 0)
				fc_off = fc_att->attcacheoff;
			else if (fc_att->attlen == -1)
			{
				/*
				 * 只有当偏移量已经对齐合适时，才能缓存
				 * 变长属性的偏移量，这样在任何情况下都不会有
				 * 填充字节：那么偏移量对
				 * 对齐或未对齐的值都是有效的。
				 */
				if (fc_usecache &&
					fc_off == att_align_nominal(fc_off, fc_att->attalign))
					fc_att->attcacheoff = fc_off;
				else
				{
					fc_off = att_align_pointer(fc_off, fc_att->attalign, -1,
											fc_tp + fc_off);
					fc_usecache = false;
				}
			}
			else
			{
				/* 不是 varlena，因此可以安全使用 att_align_nominal */
				fc_off = att_align_nominal(fc_off, fc_att->attalign);

				if (fc_usecache)
					fc_att->attcacheoff = fc_off;
			}

			if (fc_i == fc_attnum)
				break;

			fc_off = att_addlength_pointer(fc_off, fc_att->attlen, fc_tp + fc_off);

			if (fc_usecache && fc_att->attlen <= 0)
				fc_usecache = false;
		}
	}

	return fetchatt(TupleDescAttr(fc_tupleDesc, fc_attnum), fc_tp + fc_off);
}

/*
 * 将索引元组转换为 Datum/isnull 数组。
 *
 * 调用者必须为输出数组分配足够的存储。
 * (INDEX_MAX_KEYS 条目应该足够。)
 *
 * 这几乎与 heap_deform_tuple() 相同，但针对 IndexTuples。
 * 一个区别是元组永远不应该缺少任何列。
 */
void index_deform_tuple(IndexTuple fc_tup, TupleDesc fc_tupleDescriptor,
				   Datum *fc_values, bool *fc_isnull)
{
	char	   *fc_tp;				/* 指向元组数据的指针 */
	bits8	   *fc_bp;				/* 指向元组中 null 位图的指针 */

	/* XXX "知道" t_bits 在固定元组头之后！ */
	fc_bp = (bits8 *) ((char *) fc_tup + sizeof(IndexTupleData));

	fc_tp = (char *) fc_tup + IndexInfoFindDataOffset(fc_tup->t_info);

	index_deform_tuple_internal(fc_tupleDescriptor, fc_values, fc_isnull,
								fc_tp, fc_bp, IndexTupleHasNulls(fc_tup));
}

/*
 * 将索引元组转换为 Datum/isnull 数组，
 * 而不假设索引元组头的任何特定布局。
 *
 * 调用者必须提供指向数据区域的指针、null 位图的指针
 * （如果 !hasnulls 则可以为 NULL），以及 hasnulls 标志。
 */
void index_deform_tuple_internal(TupleDesc fc_tupleDescriptor,
							Datum *fc_values, bool *fc_isnull,
							char *fc_tp, bits8 *fc_bp, int fc_hasnulls)
{
	int			fc_natts = fc_tupleDescriptor->natts; /* 要提取的属性数量 */
	int			fc_attnum;
	int			fc_off = 0;		/* 在元组数据中的偏移量 */
	bool		fc_slow = false;	/* 我们可以使用/设置 attcacheoff 吗？ */

	/* 断言以保护分配固定大小数组的调用者 */
	Assert(fc_natts <= INDEX_MAX_KEYS);

	for (fc_attnum = 0; fc_attnum < fc_natts; fc_attnum++)
	{
		Form_pg_attribute fc_thisatt = TupleDescAttr(fc_tupleDescriptor, fc_attnum);

		if (fc_hasnulls && att_isnull(fc_attnum, fc_bp))
		{
			fc_values[fc_attnum] = (Datum) 0;
			fc_isnull[fc_attnum] = true;
			fc_slow = true;		/* 不能再使用 attcacheoff */
			continue;
		}

		fc_isnull[fc_attnum] = false;

		if (!fc_slow && fc_thisatt->attcacheoff >= 0)
			fc_off = fc_thisatt->attcacheoff;
		else if (fc_thisatt->attlen == -1)
		{
			/*
			 * 我们只能缓存 varlena 属性的偏移量，如果偏移量已经适当对齐，
			 * 以确保在任何情况下都没有填充字节：这样偏移量将适用于对齐或未对齐的值。
			 */
			if (!fc_slow &&
				fc_off == att_align_nominal(fc_off, fc_thisatt->attalign))
				fc_thisatt->attcacheoff = fc_off;
			else
			{
				fc_off = att_align_pointer(fc_off, fc_thisatt->attalign, -1,
										fc_tp + fc_off);
				fc_slow = true;
			}
		}
		else
		{
			/* 不是 varlena，因此可以安全使用 att_align_nominal */
			fc_off = att_align_nominal(fc_off, fc_thisatt->attalign);

			if (!fc_slow)
				fc_thisatt->attcacheoff = fc_off;
		}

		fc_values[fc_attnum] = fetchatt(fc_thisatt, fc_tp + fc_off);

		fc_off = att_addlength_pointer(fc_off, fc_thisatt->attlen, fc_tp + fc_off);

		if (fc_thisatt->attlen <= 0)
			fc_slow = true;		/* 不能再使用 attcacheoff */
	}
}

/*
 * 创建一个 palloc' 的索引元组副本。
 */
IndexTuple CopyIndexTuple(IndexTuple fc_source)
{
	IndexTuple	fc_result;
	Size		fc_size;

	fc_size = IndexTupleSize(fc_source);
	fc_result = (IndexTuple) palloc(fc_size);
	memcpy(fc_result, fc_source, fc_size);
	return fc_result;
}

/*
 * 创建一个 palloc' 的索引元组副本，只保留前面
 * leavenatts 个属性。
 *
 * 截断保证生成的索引元组不会比原始的更大。
 * 可以安全地使用带有原始元组描述符的 IndexTuple，
 * 但是调用者必须避免实际上访问从返回的元组中截断的属性！
 * 实际上，这意味着 index_getattr() 必须谨慎调用，
 * 且截断的元组只能被调用者直接控制的代码访问。
 *
 * 在持有缓冲区锁的情况下安全调用此函数，因为它
 * 从不执行外部表访问。如果将来有可能让索引元组包含
 * EXTERNAL TOAST 值，则必须重新考虑这一点。
 */
IndexTuple index_truncate_tuple(TupleDesc fc_sourceDescriptor, IndexTuple fc_source,
					 int fc_leavenatts)
{
	TupleDesc	fc_truncdesc;
	Datum		fc_values[INDEX_MAX_KEYS];
	bool		fc_isnull[INDEX_MAX_KEYS];
	IndexTuple	fc_truncated;

	Assert(fc_leavenatts <= fc_sourceDescriptor->natts);

	/* 简单情况：实际上不需要截断 */
	if (fc_leavenatts == fc_sourceDescriptor->natts)
		return CopyIndexTuple(fc_source);

	/* 创建临时描述符以进行操作 */
	fc_truncdesc = palloc(TupleDescSize(fc_sourceDescriptor));
	TupleDescCopy(fc_truncdesc, fc_sourceDescriptor);
	fc_truncdesc->natts = fc_leavenatts;

	/* 变形，形成具有较少属性的元组副本 */
	index_deform_tuple(fc_source, fc_truncdesc, fc_values, fc_isnull);
	fc_truncated = index_form_tuple(fc_truncdesc, fc_values, fc_isnull);
	fc_truncated->t_tid = fc_source->t_tid;
	Assert(IndexTupleSize(fc_truncated) <= IndexTupleSize(fc_source));

	/*
	 * 在这里不能泄漏内存，TupleDescCopy() 不会分配任何内部
	 * 结构，因此，直接 pfree() 应该清理所有分配的内存
	 */
	pfree(fc_truncdesc);

	return fc_truncated;
}
